﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Diagnostics;
using System.Windows.Threading;
using System.Timers;
using System.Threading;

namespace Master
{
    /**
     * CarManager keeps track of all cars that have been added to the map. 
     * Since there is only need for 1 CarManager this class has been implemented using the Singleton Patters.
     * Simply call CarManager.getInstance() to obtain the CarManager instance.
     * */
    public class CarManager
    {
        /*************
         * VARIABLES *
         *************/
        private static CarManager INSTANCE = new CarManager();
        private List<Thread> addCarThreads = new List<Thread>();
        private List<Car> cars;
        private Random random = new Random();

        /****************
         * CONSTRUCTORS *
         ****************/
        private CarManager()
        {
            cars = new List<Car>();
        }

        /********************
         * PUBLIC FUNCTIONS *
         ********************/
        public static CarManager getInstance()
        {
            return INSTANCE;
        }

        public void hookupSurfaceWindowEvent()
        {
            SurfaceWindow1.INSTANCE.Closed += stop;
        }

        public void stop(object sender, EventArgs e)
        {
            stopCars();
            stopAddCarThreads();
        }

        public void addSingleCar(SpawnPoint spawnPoint)
        {
            Point startPosition = new Point(-300, -300);
            Car car = createCar(spawnPoint, startPosition);
            positionCarAtSpawnPoint(spawnPoint, startPosition, car);
            car.startDriving();
            car.CarLeftMap += testcar_CarLeftMap;
            cars.Add(car);
        }


        public void addCars(SpawnPoint spawnPoint, int numberOfCars)
        {
            Thread t = new Thread(delegate() { doCreateCars(spawnPoint, numberOfCars); });
            addCarThreads.Add(t);
            t.Start();
        }

        /*********************
         * PRIVATE FUNCTIONS *
         *********************/
        private void stopCars()
        {
            foreach (Car car in cars)
            {
                car.stopDriving();
                car.CarLeftMap -= testcar_CarLeftMap;
                Map.INSTANCE.Children.Remove(car);
            }
            cars = new List<Car>();
        }

        private void stopAddCarThreads()
        {
            foreach (Thread addCarThread in addCarThreads)
                addCarThread.Abort();
            addCarThreads = new List<Thread>();
        }

        private void doCreateCars(SpawnPoint spawnPoint, int numberOfCars)
        {
            while (numberOfCars > 0)
            {
                Map.INSTANCE.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate()
                {
                    Point startPosition = new Point(-300, -300);
                    Car car = createCar(spawnPoint, startPosition);
                    positionCarAtSpawnPoint(spawnPoint, startPosition, car);
                    if (getCarAtPoint(car.getFrontBumperPoint()) != null || getCarAtPoint(car.getRearBumperPoint()) != null)
                    {
                        removeCar(car);
                        return;
                    }
                    car.startDriving();
                    car.CarLeftMap += testcar_CarLeftMap;
                    cars.Add(car);
                    numberOfCars--;
                }));
                Thread.Sleep(1000);
            }
        }

        private Car createCar(SpawnPoint spawnPoint, Point startPosition)
        {
            Car car = CarFactory.createRandomCar();
            Map.INSTANCE.Children.Add(car);
            car.Orientation = spawnPoint.getLane().Orientation;
            car.Position = startPosition;
            Map.INSTANCE.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate() { car.updateTrueBounds(Toolkit.getBounds(car)); }));
            return car;
            
        }

        private void positionCarAtSpawnPoint(SpawnPoint spawnPoint, Point startPosition, Car car)
        {
            Rect bounds = car.getTrueBounds();
            Point currentCenterPoint = new Point((bounds.Left + bounds.Right) / 2, (bounds.Top + bounds.Bottom) / 2);
            Point wantedCenterPoint = spawnPoint.getPoint(car);
            car.Position = new Point(startPosition.X + (wantedCenterPoint.X - currentCenterPoint.X), startPosition.Y + (wantedCenterPoint.Y - currentCenterPoint.Y));
            Map.INSTANCE.Dispatcher.Invoke(DispatcherPriority.Render, new Action(delegate() { car.updateTrueBounds(Toolkit.getBounds(car)); }));
        }

        public void removeCar(Car car)
        {
            car.stopDriving();
            car.CarLeftMap -= testcar_CarLeftMap;
            Map.INSTANCE.Children.Remove(car);
            cars.Remove(car);
        }

        public Car getCarAtPoint(Point p)
        {
            foreach (Car car in cars)
            {
                Rect bounds = car.getTrueBounds();
                if (p.X < bounds.Left || p.X > bounds.Right ||
                    p.Y < bounds.Top || p.Y > bounds.Bottom)
                    continue;
                return car;
            }
            return null;
        }

        private void testcar_CarLeftMap(object sender, CarLeftMapEventArgs e)
        {
            Car car = (Car) sender;
            Trace.WriteLine("Removed car at point: " + car.Position);
            removeCar(car);
        }
    }
}
